import { defineGlobalProperty, interceptRoom, isRoomInterval, roomLog } from '@/ayaka'
import StructureCache from '@/modules/structureCache'
import TransportTaskManager from '@/room/task/base/transport'
import { MoreToOneTransportData, OneToMoreTransportData } from '@/room/task/base/transport/types'
import { reactionSource } from '@/utils/constants'
import { LabBoostData, LabMemory, LabReactionTask } from './types'
import { LabTarget } from '@/setting'
import { goTo } from '@/modules/move'
import PCTaskManager from '@/room/task/pc'

/**
 * lab 控制器
 */
export default class LabController {
    /**
     * 最低合成数量，仓库中高于底物高于该数量才会合成
     */
    public static readonly MIN_REACTION_AMOUNT = 2000

    /**
     * 最低 lab 能量，低于该能量就会发布能量搬运任务
     */
    public static readonly MIN_LAB_ENERGY = Math.min(LAB_ENERGY_CAPACITY, MAX_CREEP_SIZE * LAB_BOOST_ENERGY * 1.5)

    /**
     * lab 能量搬运任务名字
     */
    public static readonly LAB_ENERGY_TASK_NAME = 'Lab搬运能量'

    /**
     * lab 反应底物搬运任务名字
     */
    public static readonly LAB_REACTION_TASK_NAME = 'Lab搬运底物'

    /**
     * lab boost 搬运任务名字
     */
    public static readonly LAB_BOOST_TASK_NAME = 'Lab搬运boost资源'

    /**
     * lab 清理任务名字
     */
    public static readonly LAB_CLEAR_TASK_NAME = 'Lab清理资源'

    /**
     * lab 清理 boost 冲突资源名字
     */
    public static readonly LAB_CLEAR_BOOST_TASK_NAME = 'Lab清理boost资源'

    /**
     * 不可实例化
     */
    protected constructor() {
        throw new Error('LabController 不可实例化')
    }

    /**
     * 运行
     * TODO：单房10lab不合成时0.2cpu，合成时3cpu，还有很大优化空间！比如，不要提前获取lab，预测冷却时间
     */
    public static run(room: MyRoom) {
        // 每 5 tick 运行一次，因为反应的最短周期是 5 tick
        if (room.controller.level < 6 || !isRoomInterval(room, 5)) return

        const memory = LabController.getMemory(room)

        const allLabs: StructureLab[] = []
        const inLabs: StructureLab[] = []
        const boostLabs: StructureLab[] = []
        const outLabs: StructureLab[] = []
        // inLab 是否可以进行反应
        let canReaction = true

        Object.keys(memory.inLab).forEach((id: Id<StructureLab>) => {
            const lab = Game.getObjectById(id)
            const labInfo = memory.inLab[id]

            if (!lab) {
                delete memory.inLab[id]
                return
            }

            inLabs.push(lab)
            allLabs.push(lab)

            if (labInfo.boost.type) {
                boostLabs.push(lab)
                canReaction = false
            }
        })

        if (canReaction) {
            canReaction = LabController.reactionPlan(room, inLabs, memory)
        }

        Object.keys(memory.outLab).forEach((id: Id<StructureLab>) => {
            const lab = Game.getObjectById(id)
            const labInfo = memory.outLab[id]

            if (!lab) {
                delete memory.outLab[id]
                return
            }

            allLabs.push(lab)

            if (labInfo.boost.type) {
                boostLabs.push(lab)
                return
            }

            if (!labInfo.forbidReaction) {
                outLabs.push(lab)
            }
        })

        if (outLabs.length && canReaction) {
            LabController.reaction(room, outLabs, inLabs, memory)
        }

        if (boostLabs.length) {
            LabController.boost(room, boostLabs, memory)
        }

        // 15 tick 运行一次
        if (allLabs.length && isRoomInterval(room, 15)) {
            LabController.checkEnergy(room, allLabs)
        }
    }

    /**
     * 获取 memory
     */
    public static getMemory(room: MyRoom) {
        if (!room.memory.lab) {
            room.memory.lab = {
                inLab: {},
                outLab: {},
                tasks: [],
            }
        }
        return room.memory.lab
    }

    /**
     * 合成规划
     */
    public static reactionPlan(room: MyRoom, inLabs: StructureLab[], memory: LabMemory) {
        if (!room.storage) return false
        const storage = room.storage

        // 没有反应物类型，说明尚未开始反应，选择合适的反应目标
        if (inLabs.find(({ id }) => !memory.inLab[id].reaction.type)) {
            // 有 lab 清理任务，说明刚合成完一批，等清理完再进行合成规划
            if (TransportTaskManager.searchTransportTaskByName(room, LabController.LAB_CLEAR_TASK_NAME).length) {
                return false
            }

            // 先看 lab 任务队列
            let target: MineralCompoundConstant | undefined = undefined
            if (memory.tasks.length) {
                memory.tasks.find((task) => {
                    if (storage.store[task.type] >= task.amount) return
                    return (target = LabController.findPreSubstrate(storage, task.type))
                })
            }

            if (!target) {
                target = LabTarget.find((r) => {
                    if (storage.store[r.target] >= r.number) return false

                    const [res1, res2] = reactionSource[r.target]
                    return (
                        storage.store[res1] >= LabController.MIN_REACTION_AMOUNT &&
                        storage.store[res2] >= LabController.MIN_REACTION_AMOUNT
                    )
                })?.target
            }

            if (!target) {
                return false
            }

            inLabs.forEach(({ id }, index) => {
                memory.inLab[id].reaction = {
                    // 回调函数内 target 可能变为 undefined
                    // eslint-disable-next-line
                    type: reactionSource[target!!!!!!!!][index],
                }
            })

            roomLog(room, `准备合成 ${target}`, 'lab', 'green')
        }

        const sourceInfo: MoreToOneTransportData['sourceInfo'] = []
        const targetInfo: OneToMoreTransportData['targetInfo'] = []
        let readyNumber = 0
        // 此时 lab 合成目标规划好了，检查自己的资源是否到位
        for (const inLab of inLabs) {
            const reaction = memory.inLab[inLab.id].reaction
            const type = reaction.type!
            const amount = reaction.amount

            if (!amount || amount < LabController.MIN_REACTION_AMOUNT) {
                const mineralType = inLab.mineralType

                if (mineralType && mineralType !== type) {
                    if (
                        !TransportTaskManager.searchTransportTaskByName(room, LabController.LAB_CLEAR_TASK_NAME).length
                    ) {
                        // 清理 inLab
                        sourceInfo.push({
                            id: inLab.id,
                            type: mineralType,
                            require: inLab.store[mineralType],
                            has: 0,
                        })
                    }
                } else if (inLab.store[type] < LabController.MIN_REACTION_AMOUNT) {
                    if (
                        !TransportTaskManager.searchTransportTaskByName(room, LabController.LAB_REACTION_TASK_NAME)
                            .length
                    ) {
                        const require = Math.min(
                            LabController.MIN_REACTION_AMOUNT - inLab.store[type],
                            inLab.store.getFreeCapacity(type) || 3000,
                        )
                        if (storage.store[type] < require) {
                            // 资源不足，放弃本次合成目标
                            inLabs.forEach(({ id }) => {
                                memory.inLab[id].reaction = {}
                            })
                            return false
                        }
                        // 搬运底物
                        targetInfo.push({
                            id: inLab.id,
                            type,
                            require,
                            has: 0,
                        })
                    }
                } else {
                    reaction.amount = LabController.MIN_REACTION_AMOUNT
                    readyNumber++
                }
            } else {
                readyNumber++
            }
        }

        if (sourceInfo.length) {
            const emptyStruct = LabController.getEmptyStructure(room)
            if (emptyStruct) {
                TransportTaskManager.addMoreToOneTransportTask(room, LabController.LAB_CLEAR_TASK_NAME, {
                    targetId: emptyStruct.id,
                    sourceInfo,
                })
            }
        }
        if (targetInfo.length) {
            TransportTaskManager.addOneToMoreTransportTask(room, LabController.LAB_REACTION_TASK_NAME, {
                sourceId: storage.id,
                targetInfo,
            })
        }

        return readyNumber === inLabs.length
    }

    /**
     * lab 反应
     */
    public static reaction(room: MyRoom, outLabs: StructureLab[], inLabs: StructureLab[], memory: LabMemory) {
        if (Game.cpu.bucket < 100 && !Memory.meta.forceLab) return

        const sourceInfo: MoreToOneTransportData['sourceInfo'] = []
        // 是否可以请求 pc
        let rpc =
            room.controller.level === 8 &&
            PCTaskManager.hasPc(room as MyRoom, PWR_OPERATE_LAB) &&
            room.storage &&
            room.storage.store['ops'] > 3000

        for (const outLab of outLabs) {
            if (outLab.cooldown) continue

            const result = outLab.runReaction(inLabs[0], inLabs[1])
            if (result === OK) {
                if (
                    rpc &&
                    (!outLab.effects?.length || outLab.effects[0].ticksRemaining < 15) &&
                    !PCTaskManager.getTaskByName(room, `${PWR_OPERATE_LAB}-${outLab.id}`)
                ) {
                    PCTaskManager.addTask(room, `${PWR_OPERATE_LAB}-${outLab.id}`, PWR_OPERATE_LAB, outLab.id)
                    rpc = false
                }
            } else if (result === ERR_NOT_ENOUGH_RESOURCES || result === ERR_INVALID_ARGS || result === ERR_FULL) {
                if (result === ERR_NOT_ENOUGH_RESOURCES) {
                    // 底物不足，删除反应目标
                    inLabs.forEach(({ id }) => (memory.inLab[id].reaction = {}))
                }

                if (
                    outLab.mineralType &&
                    !TransportTaskManager.searchTransportTaskByName(room, LabController.LAB_CLEAR_TASK_NAME).length
                ) {
                    sourceInfo.push({
                        id: outLab.id,
                        type: outLab.mineralType,
                        require: outLab.store[outLab.mineralType!],
                        has: 0,
                    })
                }
            }
        }

        if (sourceInfo.length) {
            const emptyStruct = LabController.getEmptyStructure(room)
            if (emptyStruct) {
                TransportTaskManager.addMoreToOneTransportTask(room, LabController.LAB_CLEAR_TASK_NAME, {
                    targetId: emptyStruct.id,
                    sourceInfo,
                })
            }
        }
    }

    /**
     * lab boost
     */
    public static boost(room: MyRoom, boostLabs: StructureLab[], memory: LabMemory) {
        if (TransportTaskManager.searchTransportTaskByName(room, LabController.LAB_CLEAR_BOOST_TASK_NAME).length) {
            return
        }

        const sourceInfo: MoreToOneTransportData['sourceInfo'] = []
        const targetInfo: OneToMoreTransportData['targetInfo'] = []

        for (const boostLab of boostLabs) {
            const boostInfo = memory.outLab[boostLab.id]?.boost || memory.inLab[boostLab.id]?.boost
            // 清理 boostLab
            if (boostLab.mineralType && boostLab.mineralType !== boostInfo.type) {
                sourceInfo.push({
                    id: boostLab.id,
                    type: boostLab.mineralType,
                    require: boostLab.store[boostLab.mineralType],
                    has: 0,
                })
            }

            // 更新 boost 信息
            for (const creepInfo of boostInfo.creeps) {
                const creep = Game.creeps[creepInfo.name]
                if (creep && (!creep.memory.boost || !creep.memory.boost.done)) continue
                boostInfo.require! -= creepInfo.require
                boostInfo.creeps = boostInfo.creeps.filter(({ name }) => name !== creepInfo.name)
            }

            if (!boostInfo.require || boostInfo.require <= 0) {
                delete boostInfo.type
                delete boostInfo.require
                boostInfo.creeps = []
                continue
            }

            // 搬运 boost 资源
            const boostType = boostInfo.type!
            const require = Math.min(
                boostInfo.require - boostLab.store[boostType],
                LAB_MINERAL_CAPACITY - boostLab.store[boostType],
            )
            if (require > 0 && room.storage && room.storage.store[boostType] >= require) {
                if (TransportTaskManager.searchTransportTaskByName(room, LabController.LAB_BOOST_TASK_NAME).length) {
                    continue
                }
                targetInfo.push({
                    id: boostLab.id,
                    type: boostType,
                    require,
                    has: 0,
                })
            }
        }

        if (sourceInfo.length) {
            const emptyStruct = LabController.getEmptyStructure(room)
            if (emptyStruct) {
                TransportTaskManager.addMoreToOneTransportTask(
                    room,
                    LabController.LAB_CLEAR_BOOST_TASK_NAME,
                    {
                        targetId: emptyStruct.id,
                        sourceInfo,
                    },
                    3,
                )
            }
            return
        }

        if (targetInfo.length) {
            TransportTaskManager.addOneToMoreTransportTask(
                room,
                LabController.LAB_BOOST_TASK_NAME,
                {
                    sourceId: room.storage!.id,
                    targetInfo,
                },
                4,
            )
        }
    }

    /**
     * 添加反应任务
     */
    public static addReactionTask(room: MyRoom, reaction: LabReactionTask) {
        const { tasks } = LabController.getMemory(room)
        const index = tasks.findIndex((task) => task.priority > reaction.priority)
        if (~index) {
            tasks.splice(index, 0, reaction)
        } else {
            tasks.push(reaction)
        }
    }

    /**
     * 手动添加反应任务
     */
    @interceptRoom()
    public static addReaction(roomName: string, type: MineralCompoundConstant, amount: number, priority = 5) {
        const room = Game.rooms[roomName] as MyRoom
        if (!LabController.isCompound(type)) {
            return roomLog(room, `添加反应任务失败，type 参数错误，应为化合物`, 'lab', 'red')
        }
        if (typeof amount !== 'number' || amount <= 0) {
            return roomLog(room, `添加反应任务失败，amount 参数错误，应为大于 0 的整数`, 'lab', 'red')
        }
        if (typeof priority !== 'number' || priority < 0 || priority > 10) {
            return roomLog(room, `添加反应任务失败，priority 参数错误，应为 0 ~ 10 之间的整数`, 'lab', 'red')
        }
        const task = {
            type,
            amount,
            priority,
        }
        LabController.addReactionTask(room, task)
        return roomLog(room, `添加反应任务成功，类型：${type}  数量：${amount} 优先级：${priority}`, 'lab', 'green')
    }

    /**
     * 修改反应任务
     */
    public static changeReactionTask(room: MyRoom, task: LabReactionTask) {
        const { tasks } = LabController.getMemory(room)

        const index = tasks.findIndex((t) => t.type === task.type)
        // 不存在则添加
        if (!~index) {
            LabController.addReactionTask(room, task)
            return
        }

        // 数量小于等于 0 时移除任务
        if (task.amount <= 0) {
            tasks.splice(index, 1)
            return
        }

        // 优先级相同则只修改数量
        if (task.priority === tasks[index].priority) {
            tasks[index].amount = task.amount
            return
        }

        // 否则先移除再添加，确保任务队列顺序正确
        tasks.splice(index, 1)
        LabController.addReactionTask(room, task)
    }

    /**
     * 手动修改反应任务
     */
    @interceptRoom()
    public static changeReaction(roomName: string, type: MineralBaseCompoundsConstant, amount: number, priority = 5) {
        const room = Game.rooms[roomName] as MyRoom
        if (!LabController.isCompound(type)) {
            return roomLog(room, `修改反应任务失败，type 参数错误，应为化合物`, 'lab', 'red')
        }
        if (typeof amount !== 'number') {
            return roomLog(room, `修改反应任务失败，amount 参数错误，应为整数`, 'lab', 'red')
        }
        if (typeof priority !== 'number' || priority < 0 || priority > 10) {
            return roomLog(room, `修改反应任务失败，priority 参数错误，应为 0 ~ 10 之间的整数`, 'lab', 'red')
        }
        const task = {
            type,
            amount,
            priority,
        }
        LabController.changeReactionTask(room, task)
        return roomLog(room, `修改反应任务成功，类型：${type}  数量：${amount} 优先级：${priority}`, 'lab', 'green')
    }

    /**
     * 检查 lab集群能量是否足够并发布能量搬运任务
     */
    public static checkEnergy(room: MyRoom, labs: StructureLab[]) {
        const hungryLabs = labs.filter((lab) => lab.store[RESOURCE_ENERGY] < LabController.MIN_LAB_ENERGY)
        if (!hungryLabs.length) return

        if (TransportTaskManager.searchTransportTaskByName(room, LabController.LAB_ENERGY_TASK_NAME).length) return

        let source: StructureStorage | StructureTerminal | null = null
        if (room.storage && room.storage.store[RESOURCE_ENERGY] > 10000) source = room.storage
        else if (room.terminal && room.terminal.store[RESOURCE_ENERGY] > 10000) source = room.terminal
        if (!source) return

        // 构建 lab 能量搬运任务
        const data: OneToMoreTransportData = {
            sourceId: source.id,
            targetInfo: hungryLabs.map((lab) => ({
                id: lab.id,
                type: RESOURCE_ENERGY,
                require: lab.store.getFreeCapacity('energy'),
                has: 0,
            })),
        }
        TransportTaskManager.addOneToMoreTransportTask(room, LabController.LAB_ENERGY_TASK_NAME, data)
    }

    /**
     * 注册 lab
     */
    public static registerLab(room: MyRoom, labs: StructureLab[]) {
        const memory = LabController.getMemory(room)
        let inLabNumber = 0

        labs.forEach(({ id: labId, pos }) => {
            // 距离每个 lab 2 格以内的 lab 就是 inLab
            if (inLabNumber < 2 && labs.every((l) => l.pos.inRangeTo(pos, 2))) {
                if (!memory.inLab[labId]) {
                    memory.inLab[labId] = { reaction: {}, boost: { creeps: [] } }
                }

                if (memory.outLab[labId]) {
                    memory.inLab[labId] = {
                        reaction: {},
                        boost: memory.outLab[labId].boost,
                    }
                    delete memory.outLab[labId]
                }

                inLabNumber++
                return
            }

            // 否则就是 outLab
            if (!memory.outLab[labId]) {
                memory.outLab[labId] = { boost: { creeps: [] } }
            }

            if (memory.inLab[labId]) {
                memory.outLab[labId] = {
                    boost: memory.inLab[labId].boost,
                }
                delete memory.inLab[labId]
            }
        })
    }

    /**
     * 挂载
     */
    public static mount() {
        StructureCache.onNewStruct(function registerLab(room, newStructMap) {
            const labs = newStructMap[STRUCTURE_LAB]
            if (labs) LabController.registerLab(room, labs)
        })
    }

    /**
     * 判断资源是否是化合物
     */
    public static isCompound = (resource: ResourceConstant): resource is MineralCompoundConstant => {
        return resource in REACTION_TIME
    }

    /**
     * 查找合成一个化合物要先合成的底物，大于 2000 就可以
     * @param compound 化合物
     * @returns 底物
     */
    public static findPreSubstrate = (
        storage: StructureStorage,
        compound: MineralConstant | MineralCompoundConstant,
    ): MineralBoostConstant | MineralBaseCompoundsConstant | undefined => {
        if (!LabController.isCompound(compound)) return undefined
        const substrates = reactionSource[compound]
        for (let index = 0; index < substrates.length; index++) {
            if (storage.store[substrates[index]] >= LabController.MIN_REACTION_AMOUNT) continue
            return LabController.findPreSubstrate(storage, substrates[index])
        }
        return compound as MineralBoostConstant | MineralBaseCompoundsConstant
    }

    /**
     * 获得有空闲空间的容器
     */
    public static getEmptyStructure = (room: MyRoom): StructureStorage | StructureTerminal | null => {
        if (room.storage && room.storage.store.getFreeCapacity() > 10000) return room.storage
        if (room.terminal && room.terminal.store.getFreeCapacity() > 10000) return room.terminal
        return null
    }

    /**
     * 找到有指定 boost 资源的 lab
     */
    public static findBoostLab(room: MyRoom, boostType: ResourceConstant) {
        const memory = LabController.getMemory(room)
        for (const labType of ['outLab', 'inLab'] as const) {
            for (const labId of Object.keys(memory[labType])) {
                const boostInfo = memory[labType][labId as Id<StructureLab>].boost
                if (!boostInfo.type || boostInfo.type !== boostType) continue
                return labId as Id<StructureLab>
            }
        }
        return undefined
    }

    /**
     * 找一个可用于 boost 的 lab
     */
    public static findBoostFreeLab(room: MyRoom) {
        const memory = LabController.getMemory(room)
        for (const labType of ['outLab', 'inLab'] as const) {
            for (const { boost } of Object.values(memory[labType])) {
                const boostData = boost as LabBoostData
                if (!boostData.type) return boostData
            }
        }
        return undefined
    }

    /**
     * boost Creep
     */
    public static boostCreep(creep: Creep) {
        const room = creep.room as MyRoom

        const { data: creepBoostData } = creep.memory.boost!
        let done = true
        for (const part in creepBoostData) {
            const partBoostInfo = creepBoostData[part as BodyPartConstant]!
            if (partBoostInfo.amount >= partBoostInfo.require) continue
            const boostType = partBoostInfo.type
            done = false

            const require = partBoostInfo.require * 30
            const boostLabId = LabController.findBoostLab(room, boostType)
            if (!boostLabId) {
                // 没找到就添加
                const freeLabBoostData = LabController.findBoostFreeLab(room)
                if (!freeLabBoostData) continue
                freeLabBoostData.type = boostType
                freeLabBoostData.require = require
                freeLabBoostData.creeps.push({
                    name: creep.name,
                    require,
                })
                continue
            }

            const memory = LabController.getMemory(room)
            const labBoostData = memory.inLab[boostLabId]?.boost || memory.outLab[boostLabId]?.boost
            if (!labBoostData.creeps.find(({ name }) => name === creep.name)) {
                labBoostData.creeps.push({
                    name: creep.name,
                    require,
                })
                labBoostData.require = (labBoostData.require || 0) + require
                continue
            }

            if (creep.spawning || creep.memory.boost?.wait) continue

            const boostLab = Game.getObjectById(boostLabId)
            if (!boostLab || boostLab.store[boostType] < require || boostLab._boosted) continue

            if (!creep.pos.isNearTo(boostLab)) {
                goTo(creep, boostLab.pos)
            }

            // 需要 boost 的部件的数量
            const amount = partBoostInfo.require - partBoostInfo.amount
            const result = boostLab.boostCreep(creep, amount)
            if (result === OK) {
                boostLab._boosted = true
                partBoostInfo.amount = partBoostInfo.require
                labBoostData.creeps = labBoostData.creeps.filter(({ name }) => name !== creep.name)
                labBoostData.require = Math.max((labBoostData.require || 0) - require, 0)
            }
            return
        }

        if (done) {
            creep.memory.boost!.done = true
        }
    }
}

defineGlobalProperty('Lab', {
    value: LabController,
})
